1
Пробел в производительности: почему расширять NumPy?
AI018Lesson 5
00:00

Хотя NumPy построен на языке С, некоторые вычислительно интенсивные алгоритмы сталкиваются с стеной векторизации. Это происходит тогда, когда присущая задержка динамической природы Python превышает преимущества высокого уровня абстракции.

1. Налог интерпретатора и упаковка

Каждая итерация в стандартном цикле Python включает динамическую проверку типов и подсчет ссылок. Даже при использовании скаляров NumPy «упаковка» исходных данных С в объекты Python создает огромный узкий проход для функций, таких как $\text{logit}(p) = \log(p/(1-p))$. Обработка граничных случаев на С значительно быстрее:

>>> logit(0) -> -бесконечность
>>> logit(1) -> бесконечность
>>> logit(2) -> неопределённое значение (nan)
>>> logit(-2) -> неопределённое значение (nan)

2. Рост промежуточных массивов

Чистые выражения NumPy создают временные буферы памяти для каждой подоперации. Расширение через C-API позволяет использовать объединение ядра, где преобразование logit рассчитывается за один проход без дополнительной нагрузки на память.

3. Пространственные зависимости

Операции, связанные с паттернами доступа к соседям, такие как двумерная шаблонная операция:

$$B(I, J) = A(I, J) + (A(I-1, J) + A(I+1, J) + A(I, J-1) + A(I, J+1)) \cdot 0.5D0 + (A(I-1, J-1) + A(I-1, J+1) + A(I+1, J-1) + A(I+1, J+1)) \cdot 0.25D0$$

их сложно эффективно выразить с помощью срезов без избыточных копий памяти. Расширения на С позволяют выполнять прямую арифметику указателей с выравниванием по кэшу.

main.py
TERMINALbash — 80x24
> Ready. Click "Run" to execute.
>